Day 13: 추상 클래스와 인터페이스
추상 클래스와 인터페이스는 객체지향 설계의 핵심 도구입니다. 추상 클래스는 “부분적으로 완성된 설계도”이고, 인터페이스는 “계약서(규격)“입니다. 둘 다 직접 인스턴스를 만들 수 없으며, 자식 클래스에서 구체적으로 구현해야 합니다.
추상 클래스
abstract 키워드로 선언하며, 추상 메서드(본문 없는 메서드)와 일반 메서드를 모두 가질 수 있습니다.
abstract class GameCharacter {
String name;
int hp;
int attackPower;
GameCharacter(String name, int hp, int attackPower) {
this.name = name;
this.hp = hp;
this.attackPower = attackPower;
}
// 추상 메서드: 자식 클래스가 반드시 구현해야 함
abstract void attack(GameCharacter target);
abstract void specialSkill();
// 일반 메서드: 공통 로직
void takeDamage(int damage) {
hp -= damage;
System.out.println(name + "이(가) " + damage + " 데미지를 받음! (HP: " + hp + ")");
if (hp <= 0) {
System.out.println(name + " 쓰러짐!");
}
}
void showStatus() {
System.out.println("[" + name + "] HP: " + hp + " / 공격력: " + attackPower);
}
}
class Warrior extends GameCharacter {
Warrior(String name) {
super(name, 200, 30);
}
@Override
void attack(GameCharacter target) {
System.out.println(name + "이(가) 검으로 " + target.name + "을(를) 공격!");
target.takeDamage(attackPower);
}
@Override
void specialSkill() {
System.out.println(name + "의 분노의 일격! (공격력 2배)");
attackPower *= 2;
}
}
class Mage extends GameCharacter {
int mana;
Mage(String name) {
super(name, 100, 50);
this.mana = 150;
}
@Override
void attack(GameCharacter target) {
if (mana >= 20) {
System.out.println(name + "이(가) 파이어볼로 " + target.name + "을(를) 공격!");
target.takeDamage(attackPower);
mana -= 20;
} else {
System.out.println("마나 부족!");
}
}
@Override
void specialSkill() {
System.out.println(name + "의 대규모 메테오!");
mana -= 80;
}
}
public class AbstractClassExample {
public static void main(String[] args) {
// GameCharacter gc = new GameCharacter(...); // 에러! 추상 클래스 인스턴스 불가
Warrior warrior = new Warrior("전사 아서");
Mage mage = new Mage("마법사 멀린");
warrior.showStatus();
mage.showStatus();
warrior.attack(mage);
mage.specialSkill();
mage.attack(warrior);
}
}
인터페이스
인터페이스는 클래스가 구현해야 할 메서드 규격을 정의합니다. implements로 구현하며, 다중 구현이 가능합니다.
interface Flyable {
void fly();
int getMaxAltitude();
}
interface Swimmable {
void swim();
int getMaxDepth();
}
interface Runnable {
void run();
int getMaxSpeed();
}
// 다중 인터페이스 구현
class Duck implements Flyable, Swimmable, Runnable {
String name;
Duck(String name) {
this.name = name;
}
@Override
public void fly() {
System.out.println(name + "이(가) 하늘을 날아갑니다!");
}
@Override
public int getMaxAltitude() {
return 500;
}
@Override
public void swim() {
System.out.println(name + "이(가) 물 위에서 헤엄칩니다!");
}
@Override
public int getMaxDepth() {
return 2;
}
@Override
public void run() {
System.out.println(name + "이(가) 뒤뚱뒤뚱 달립니다!");
}
@Override
public int getMaxSpeed() {
return 10;
}
}
class Penguin implements Swimmable, Runnable {
String name;
Penguin(String name) {
this.name = name;
}
@Override
public void swim() {
System.out.println(name + "이(가) 빠르게 수영합니다!");
}
@Override
public int getMaxDepth() {
return 100;
}
@Override
public void run() {
System.out.println(name + "이(가) 쪼르르 걸어갑니다!");
}
@Override
public int getMaxSpeed() {
return 5;
}
}
public class InterfaceExample {
// 인터페이스 타입으로 매개변수 선언 (다형성)
static void letItFly(Flyable f) {
f.fly();
System.out.println("최대 고도: " + f.getMaxAltitude() + "m");
}
static void letItSwim(Swimmable s) {
s.swim();
System.out.println("최대 수심: " + s.getMaxDepth() + "m");
}
public static void main(String[] args) {
Duck duck = new Duck("도널드");
Penguin penguin = new Penguin("뽀로로");
letItFly(duck);
letItSwim(duck);
letItSwim(penguin);
// letItFly(penguin); // 컴파일 에러! Penguin은 Flyable이 아님
}
}
default 메서드와 static 메서드
Java 8부터 인터페이스에도 구현된 메서드를 정의할 수 있습니다.
interface Logger {
// 추상 메서드
void log(String message);
// default 메서드: 기본 구현 제공
default void info(String message) {
log("[INFO] " + message);
}
default void error(String message) {
log("[ERROR] " + message);
}
default void warn(String message) {
log("[WARN] " + message);
}
// static 메서드: 인터페이스 이름으로 호출
static Logger consoleLogger() {
return message -> System.out.println(message);
}
}
class FileLogger implements Logger {
@Override
public void log(String message) {
// 파일에 기록하는 대신 여기서는 콘솔에 표시
System.out.println("[FILE] " + message);
}
// 필요하면 default 메서드를 오버라이딩할 수 있음
@Override
public void error(String message) {
log("[CRITICAL ERROR] " + message);
}
}
public class DefaultMethodExample {
public static void main(String[] args) {
Logger console = Logger.consoleLogger();
console.info("서버 시작됨");
console.error("연결 실패");
console.warn("메모리 부족");
Logger fileLogger = new FileLogger();
fileLogger.info("요청 처리");
fileLogger.error("디스크 공간 부족"); // 오버라이딩된 버전
}
}
추상 클래스 vs 인터페이스 비교
// 추상 클래스: "~은 ~이다" (is-a 관계)
// - 상태(필드)를 가질 수 있음
// - 생성자를 가질 수 있음
// - 단일 상속만 가능
// - 공통 로직 + 강제 구현 메서드가 필요할 때
abstract class Vehicle {
int speed;
abstract void move();
void stop() { speed = 0; }
}
// 인터페이스: "~을 할 수 있다" (can-do 관계)
// - 상태를 가질 수 없음 (상수만 가능)
// - 생성자 없음
// - 다중 구현 가능
// - 기능(역할)을 정의할 때
interface Chargeable {
void charge();
int getBatteryLevel();
}
interface GPSEnabled {
String getCurrentLocation();
}
// 조합: 추상 클래스 상속 + 인터페이스 다중 구현
class ElectricScooter extends Vehicle implements Chargeable, GPSEnabled {
int battery = 100;
@Override
void move() { System.out.println("전동킥보드가 달립니다!"); }
@Override
public void charge() { battery = 100; }
@Override
public int getBatteryLevel() { return battery; }
@Override
public String getCurrentLocation() { return "서울시 강남구"; }
}
오늘의 연습문제
-
결제 시스템 설계:
Payable인터페이스(메서드:pay(long amount),refund(long amount))를 정의하고,CreditCard,BankAccount,CryptoCurrency클래스로 구현하세요. -
정렬 가능한 컬렉션:
Comparable<T>인터페이스를 구현하는Student클래스를 만드세요. 이름과 점수를 가지며, 점수 기준 내림차순으로 정렬됩니다.Arrays.sort()로 테스트하세요. -
게임 캐릭터 시스템: 추상 클래스
Character(이름, HP)와 인터페이스Healable,Buffable을 조합하세요.Paladin은 두 인터페이스를 모두 구현하고,Rogue는Buffable만 구현합니다.